using UnityEngine;
using System.Collections;
using DanielSig.UnityExtensions;


/// <summary>
/// 16 Bit Unit Vector. Very efficient way of storing a unit vector with a precision error of
/// 161 square degrees (12.7 degrees between values). This data structure has a low noise ratio (less than 1%),
/// as opposed to unit vectors in euclidean space using 3 single precision (3 * 32bits = 96bits)
/// floating point values (more than 99.99% noise). This data structure also has precisions much
/// more evenly distributed, as opposed to using eular angles where the poles have most precision.
/// This data structure is mainly designed for networking, where data efficiency is important,
/// but not the speed of computability. Use this structure for sending and storing unit vectors.
/// </summary>
namespace DanielSig.Geometry
{
	public struct UnitVector8 : I8Bit
	{
		
		public static implicit operator Vector3(UnitVector8 unitVector)
		{
			return unitVector.vector;
		}
		public static explicit operator UnitVector8(Vector3 vector)
		{
			return new UnitVector8(vector);
		}
		
		public UnitVector8(Vector3 vector)
		{
			_data = 0;
			this.vector = vector;
		}
		public UnitVector8(byte data)
		{
			_data = data;
		}
	
		public Vector3 vector
		{
			get
			{
				float u = uValue;
				float v = vValue;
				
				bool back = ((_data & U_VALUE) >> 3) + (_data & V_VALUE) < V_VALUE;
				
				if(!back)
				{
					float temp = u;
					u = 1F - v;
					v = 1F - temp;
				}
				Vector3 start = back ? Vector3.back : Vector3.forward;
				Vector3 uDir;
				Vector3 vDir;
				if((_data & UP_BIT) > 0)
				{
					if((_data & RIGHT_BIT) > 0)
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.right - Vector3.forward;
						vDir = back ? Vector3.right - Vector3.back : Vector3.up - Vector3.forward;
						if(back)
						{
							u = (u * ONE_UNIT_LESS) + ONE_UNIT;
							v = (v * ONE_UNIT_LESS) + ONE_UNIT;
						}
						else
						{
							u *= ONE_UNIT_LESS;
							v *= ONE_UNIT_LESS;
						}
					}
					else
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.left - Vector3.forward;
						vDir = back ? Vector3.left - Vector3.back : Vector3.up - Vector3.forward;
						if(back)
						{
							u += ONE_UNIT;
						}
						else
						{
							u = (u * TWO_UNITS_LESS) + ONE_UNIT;
							v = (v * TWO_UNITS_LESS);
						}
					}
				}
				else if((_data & RIGHT_BIT) > 0)
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.right - Vector3.forward;
					vDir = back ? Vector3.right - Vector3.back : Vector3.down - Vector3.forward;
					if(back)
					{
						v += ONE_UNIT;
					}
					else
					{
						u = (u * TWO_UNITS_LESS);
						v = (v * TWO_UNITS_LESS) + ONE_UNIT;
					}
				}
				else
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.left - Vector3.forward;
					vDir = back ? Vector3.left - Vector3.back : Vector3.down - Vector3.forward;
					if(back)
					{
						u = (u * ONE_UNIT_MORE);
						v = (v * ONE_UNIT_MORE);
					}
					else
					{
						u = (u * THREE_UNITS_LESS) + ONE_UNIT;
						v = (v * THREE_UNITS_LESS) + ONE_UNIT;
					}
				}
				return (start + uDir * u + vDir * v).normalized;
			}
			set
			{
				value.Normalize();
				if(value.y == 0F)
				{
					if(value.x == 0F)
					{
						if(value.z <= 0F)	_data = BACK;
						else				_data = FORWARD;
						return;
					}
					else if(value.z == 0F)
					{
						if(value.x < 0F)	_data = LEFT;
						else				_data = RIGHT;
						return;
					}
				}
				else if(value.x == 0F && value.z == 0F)
				{
					if(value.y < 0F)	_data = DOWN;
					else				_data = UP;
					return;
				}
				float minVal = 0.0000001F;
				if(value.x == 0F) value.x = value.z >= 0F ? minVal : -minVal;
				if(value.z >= 0F && value.z < HALF_UNIT) value.z = -minVal;
				bool back = value.z < 0F;
				if(back)
				{
					if(value.y < HALF_UNIT && value.y >= 0F) value.y = -minVal;
					if(value.x < HALF_UNIT && value.x >= 0F) value.x = -minVal;
				}
				else
				{
					if(value.y > -HALF_UNIT && value.y <= 0F) value.y = minVal;
					if(value.x > -HALF_UNIT && value.x <= 0F) value.x = minVal;
				}
				value.Normalize();
				
				Vector3 start = back ? Vector3.back : Vector3.forward;
				Vector3 uDir;
				Vector3 vDir;
				_data = 0;
				if(value.y > 0F)
				{
					_data |= UP_BIT;
					if(value.x > 0F)
					{
						_data |= RIGHT_BIT;
						uDir = back ? Vector3.up - Vector3.back : Vector3.up - Vector3.forward;
						vDir = back ? Vector3.right - Vector3.back : Vector3.right - Vector3.forward;
					}
					else
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.up - Vector3.forward;
						vDir = back ? Vector3.left - Vector3.back : Vector3.left - Vector3.forward;
					}
				}
				else if(value.x > 0F)
				{
					_data |= RIGHT_BIT;
					uDir = back ? Vector3.down - Vector3.back : Vector3.down - Vector3.forward;
					vDir = back ? Vector3.right - Vector3.back : Vector3.right - Vector3.forward;
				}
				else
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.down - Vector3.forward;
					vDir = back ? Vector3.left - Vector3.back : Vector3.left - Vector3.forward;
				}
				float uLen = 1F / uDir.magnitude;
				float vLen = 1F / vDir.magnitude;
				Vector3 uNormal = uDir * uLen;
				Vector3 vNormal = vDir * vLen;
				Plane p = new Plane(uNormal.Cross(vNormal).normalized, start);
				Vector3 flat = p.CutLine(Vector3.zero, value*5F);
				
				float v = Mathf.Sqrt(flat.x * flat.x * 2) * vLen;
				Vector3 r = flat - vDir * v;
				float u = (r - start).magnitude * vLen;
				
				if(value.y > 0F)
				{
					if(value.x > 0F)
					{
						if(back)
						{
							u = (u - ONE_UNIT) / ONE_UNIT_LESS;
							v = (v - ONE_UNIT) / ONE_UNIT_LESS;
						}
						else
						{
							//HÉRNA!!!!!!!
							u /= ONE_UNIT_LESS;
							v /= ONE_UNIT_LESS;
							/*if(u + v > 1F)
							{
								float temp = 1F / (u + v);
								u *= temp;
								v *= temp;
							}*/
						}
					}
					else
					{
						if(back)
						{
							u -= ONE_UNIT;
						}
						else
						{
							u = (u) / TWO_UNITS_LESS;
							v = (v - ONE_UNIT) / TWO_UNITS_LESS;
						}
					}
				}
				else if(value.x > 0F)
				{
					if(back)
					{
						v -= ONE_UNIT;
					}
					else
					{
						u = (u - ONE_UNIT) / TWO_UNITS_LESS;
						v = (v) / TWO_UNITS_LESS;
					}
				}
				else
				{
					if(back)
					{
						u /= ONE_UNIT_MORE;
						v /= ONE_UNIT_MORE;
					}
					else
					{
						u = (u - ONE_UNIT) / THREE_UNITS_LESS;
						v = (v - ONE_UNIT) / THREE_UNITS_LESS;
					}
				}
				
				if(!back)
				{
					if(u + v > 1F)
					{
						float temp = 1F / (u + v);
						u *= temp;
						v *= temp;
					}
					u = 1F - u;
					v = 1F - v;
				}
				
				byte tempU = (byte)((byte)(u * UV_MAX + 0.5F));
				byte tempV = (byte)(v * UV_MAX + 0.5F);
				if(tempU > 128) tempU = 0;//prevent underflow
				else if(tempU > 7) tempU = 7;//prevent overflow
				if(tempV > 128) tempV = 0;//prevent underflow
				else if(tempV > 7) tempV = 7;//prevent overflow
				
				_data |= (byte)((tempU << 3) | tempV);
				
				if(back && tempU + tempV >= UV_MAX) _data ^= 0x3F;//flipping the UV bits
			}
		}
		
		private float uValue
		{
			get
			{
				return ((_data >> 3) & UV_MAX) * INV_UV_MAX;
			}
			set
			{
				_data = (byte)(_data & (~U_VALUE));
				_data |= (byte)(((byte)(value * UV_MAX + 0.5F) << 3) & U_VALUE);
			}
		}
		private float vValue
		{
			get
			{
				return (_data & UV_MAX) * INV_UV_MAX;
			}
			set
			{
				_data = (byte)(_data & (~V_VALUE));
				_data |= (byte)((byte)(value * UV_MAX + 0.5F) & V_VALUE);
			}
		}
	
		public byte value
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		public byte rawData
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		public override string ToString()
		{
			return vector.ToString();
		}
		
		private byte _data;
		
		private const byte FORWARD = 0xFF;
		private const byte BACK = 0x00;
		private const byte UP = 0xB0;
		private const byte DOWN = 0x30;
		private const byte RIGHT = 0x46;
		private const byte LEFT = 0x06;
		
		/*
		1000 0000
		0100 0000
		0011 1000
		0000 0111
		*/
		private const byte UP_BIT =    0x80;
		private const byte RIGHT_BIT = 0x40;
		private const byte U_VALUE =   0x38;
		private const byte V_VALUE =   0x07;
		
		private const byte UV_MAX = V_VALUE;
		private const float INV_UV_MAX = 1F / UV_MAX;
		private const float HALF_UNIT = 0.5F * INV_UV_MAX;
		private const float ONE_UNIT = INV_UV_MAX;
		private const float TWO_UNITS = 2F * INV_UV_MAX;
		private const float ONE_UNIT_MORE = (UV_MAX + 1.155F) / UV_MAX;
		private const float ONE_UNIT_LESS = (UV_MAX - 1.155F) / UV_MAX;
		private const float TWO_UNITS_LESS = (UV_MAX - 2F) / UV_MAX;
		private const float THREE_UNITS_LESS = (UV_MAX - 3F) / UV_MAX;
	}
}